package game.Map;

import game.Audio.AudioResources;
import game.Utils.GraphicsUtils;
import game.Vector3;
import javafx.scene.image.Image;
import javafx.scene.paint.Color;
import javafx.util.Pair;

import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class Map {
    public class BombPoint{
        List<Vector3> points;

        public BombPoint(){
            points = new ArrayList<>();
        }

        public void addPoint(Vector3 point){
            points.add(point);
        }

        public List<Vector3> getPoints(){
            return points;
        }

        public Vector3 getCenter(){
            Vector3 center = new Vector3(0.0,0.0,0.0);
            for (Vector3 p: points) {
                center = center.add(p.divide(points.size()));
            }

            return center;
        }
        public double minDist2d(Vector3 point){
            double minDist = Vector3.distance2d(point, points.get(0));
            for (Vector3 p: points) {
                double dist = Vector3.distance2d(point, p);
                if(dist < minDist){
                    minDist = dist;
                }
            }
            return minDist;
        }
    }
    private List<BombPoint> bombPoints; //Tile position of bomb points
    public void addMapBombPoint(Vector3 point){
        double samePointDist = 5.0;
        for (BombPoint bp: bombPoints) {
            double dist = bp.minDist2d(point);
            if(dist <= samePointDist){
                bp.addPoint(point);
                return;
            }
        }
        BombPoint newBombPoint = new BombPoint();
        newBombPoint.addPoint(point);
        bombPoints.add(newBombPoint);

    }

    private Vector3 position;
    private int nTilesW;
    private int nTilesH;
    private double tileSize = 1.0;
    private Tile[][] data;
    private Image[] tileImages;
    private Image background;
    private int tileWH;
    private List<Vector3> tSpawn; //Tile position of terrorist spawn
    private List<Vector3> ctSpawn; //Tile position of counter terrorist spawn
    private javafx.scene.paint.Color backgroundColor;
    private String name;


    private final int chunkSize;
    int chunksW, chunksH;
    private MapChunk[][] chunks;

    public Vector3 getPosition() {
        return position;
    }

    public double getTileSize() {
        return tileSize;
    }

    public int getNumberofTilesWidth() {
        return nTilesW;
    }

    public int getNumberofTilesHeight() {
        return nTilesH;
    }

    public Map(Vector3 position, double tileSize, String path) throws IOException {
        this.position = position;
        this.tileSize = tileSize;
        tSpawn = new ArrayList<>();
        ctSpawn = new ArrayList<>();
        bombPoints = new ArrayList<>();
        this.chunkSize = 16;
        this.load(path);
        this.generateChunks();
        String[] aux = path.split("/");
        this.name = aux[aux.length - 1].split("\\.")[0];
    }

    // Creates the map chunks and renders them initially
    public void generateChunks() {
        chunksW = (int) Math.ceil((double) nTilesW / chunkSize);
        chunksH = (int) Math.ceil((double) nTilesH / chunkSize);

        chunks = new MapChunk[chunksW][chunksH];
        for (int i = 0; i < chunksW; i++) {
            for (int j = 0; j < chunksH; j++) {
                chunks[i][j] = new MapChunk(new Point(i * chunkSize, j * chunkSize), chunkSize, this);
            }
        }
    }

    public void updateChunks() {
        for (int i = 0; i < chunksW; i++) {
            for (int j = 0; j < chunksH; j++) {
                chunks[i][j].updateChunk();
            }
        }
    }

    public List<MapChunk> getChunksInArea(Vector3 topLeftCorner, Vector3 topRightCorner) {
        Vector3 leftCornerTilePos = this.globalToLocal(topLeftCorner);
        Vector3 rightCornerTilePos = this.globalToLocal(topRightCorner);

        int x0 = (int) leftCornerTilePos.x / chunkSize;
        int y0 = (int) leftCornerTilePos.y / chunkSize;
        int xf = (int) Math.ceil(rightCornerTilePos.x / chunkSize);
        int yf = (int) Math.ceil(rightCornerTilePos.y / chunkSize);

        List<MapChunk> result = new ArrayList<MapChunk>();

        // Draw tiles that are contained inside the camera rectangle
        for (int y = y0; y <= yf; y++) {
            for (int x = x0; x <= xf; x++) {
                if (x >= 0 && y >= 0 && x < chunksW && y < chunksH)
                    result.add(chunks[x][y]);
            }
        }
        return result;
    }

    public Point globalToTilePos(Vector3 pos) {
        Vector3 localPos = globalToLocal(pos);
        return new Point((int) pos.x, (int) pos.y);
    }

    public Vector3 tileToGlobalPos(Point pos) {
        Vector3 localPos = new Vector3(pos.x, pos.y, 0);
        return localToGlobal(localPos);
    }

    /**
     * @param pos Position in the global world
     * @return The value of that position in the local coordinates of the map
     */
    public Vector3 globalToLocal(Vector3 pos) {
        return pos.sub(position).divide(tileSize);
    }

    /**
     * @param pos Position in local coordinates of the map
     * @return The value of that position in global coordinates of the world
     */
    public Vector3 localToGlobal(Vector3 pos) {
        return position.add(pos.multiply(tileSize));
    }

    /**
     * Returns true if a tile is walkable by a player
     *
     * @param x X Coordinate of the tile
     * @param y Y Coordinate of the tile
     * @return Wether the tile is walkable by a player or not
     */
    public boolean isWalkable(int x, int y) {
        if (x >= 0 && x < nTilesW && y >= 0 && y < nTilesH) {
            return data[x][y].isWalkable();
        } else return false;
    }

    /**
     * Returns the tile in the specified coordinates.
     * Null if none is found.
     *
     * @param x
     * @param y
     * @return Tile in the specified coordinates. Null if none is found.
     */
    public Tile getTile(int x, int y) {
        if (x >= 0 && x < nTilesW && y >= 0 && y < nTilesH) {
            return data[x][y];
        } else return null;
    }

    public Tile getTile(Point tilePos) {
        return getTile(tilePos.x, tilePos.y);
    }

    public Tile getTile(Vector3 localPos) {
        return getTile((int) localPos.x, (int) localPos.y);
    }

    public Tile getTileAtGlobalPos(Vector3 globalPos) {
        return getTile(this.globalToLocal(globalPos));
    }

    public Image getTileImage(int x, int y) {
        if (x >= 0 && x < nTilesW && y >= 0 && y < nTilesH) {
            return tileImages[data[x][y].getTileIndex()];
        } else return null;
    }

    /**
     * Returns a list of points (a line) given two points
     *
     * @param a Starting point of the line
     * @param b Ending point of the line
     * @return
     */
    public List<Point> bresenhamsLine(Point a, Point b) {
        List<Point> line = new ArrayList<Point>();

        int dx = Math.abs(b.x - a.x);
        int dy = Math.abs(b.y - a.y);

        int sx = (a.x < b.x ? 1 : -1);
        int sy = (a.y < b.y ? 1 : -1);

        int error = dx - dy;

        int x = a.x;
        int y = a.y;

        boolean end = false;
        int iter = 0; int maxIter = dx + dy;
        while (!end && iter < maxIter) {
            line.add(new Point(x, y));

            if (x == b.x && y == b.y) {
                break;
            }

            int e2 = 2 * error;

            if (e2 > -dy) {
                error -= dy;
                x += sx;
            }
            if (e2 < dx) {
                error += dx;
                y += sy;
            }

            iter++;
        }

        return line;
    }

    public Vector3 wall_between(Vector3 a, Vector3 b) {
        Point x = globalToTilePos(a);
        Point y = globalToTilePos(b);
        Object p = wall_between(x, y);
        if (p == null) {
            return null;
        } else {
            return tileToGlobalPos((Point) p);
        }
    }


    Point wall_between(Point a, Point b) {
        List<Point> result = new ArrayList<Point>();

        List<Point> line = bresenhamsLine(a, b);
        for (int i = 0; i < line.size(); i++) {
            Point p = line.get(i);
            try {
                if (getTile(p.x, p.y).isWall()) {
                    return p;
                }
            } catch (NullPointerException e) {
                return null;
            }

        }
        return null;
    }


    /**
     * Returns a list of all tile positions that can be viewed (do not have blocking tiles in between) from a given
     * center inside a radius.
     *
     * @param center Center tile position
     * @param r      Radius of the area to cover (tiles)
     * @return
     */
    public List<Point> viewed_from(Point center, int r) {
        List<Point> result = new ArrayList<Point>();

        for (int x = -r; x <= r; x++) {
            for (int y = -r; y <= r; y++) {
                // Circular area
                if (x * x + y * y > r * r)
                    continue;

                // Check if viewable from center point
                Point p = new Point(center.x + x, center.y + y);
                if (wall_between(p, center) == null) {
                    result.add(p);
                }
            }
        }
        return result;
    }

    public boolean is_viewed_from(Point center, Point p) {
        return wall_between(p, center) == null;
    }

    public boolean is_viewed_from(Point center, Point p, double cameraAngle, double fovAngle) {
        if (center.equals(p)) {
            return true;
        }

        // Check view angles
        double pointAngle = Math.atan2(p.y - center.y, p.x - center.x);
        double angleDiff = Math.PI - Math.abs(Math.abs(pointAngle - cameraAngle) - Math.PI);

        // Distance radius
        int maxDist = Math.abs(p.y - center.y);
        maxDist = Math.max(maxDist, Math.abs(p.x - center.x));
        int viewRadius = 1;

        return wall_between(p, center) == null && (Math.abs(angleDiff) < fovAngle / 2 || maxDist <= viewRadius);
    }

    public boolean is_viewed_from(Vector3 center, Vector3 p) {
        return is_viewed_from(globalToTilePos(center), globalToTilePos(p));
    }

    public boolean is_viewed_from(Vector3 center, Vector3 p, double cameraAngle, double fovAngle) {
        return is_viewed_from(globalToTilePos(center), globalToTilePos(p), cameraAngle, fovAngle);
    }

    /**
     * Returns a list of all global tile positions that can be viewed (do not have blocking tiles in between) from a given
     * center inside a radius.
     *
     * @param center Center position in global world
     * @param r      Radius of the area to cover (m)
     * @return
     */
    public List<Vector3> viewed_from(Vector3 center, double r) {
        List<Vector3> result = new ArrayList<Vector3>();

        // Get tile positions viewable form center
        Point tilePos = globalToTilePos(center);
        List<Point> tileResults = viewed_from(tilePos, (int) Math.ceil(r / tileSize));

        // Transform tile positions to global world positions
        for (int i = 0; i < tileResults.size(); i++) {
            result.add(tileToGlobalPos(tileResults.get(i)));
        }
        return result;
    }


    /**
     * @param origin
     * @param goal
     * @param allowDiagonals
     * @return
     */
    public List<Point> a_star_local(Point origin, Point goal, boolean allowDiagonals) {
        int maxIter = 4096;
        int iter = 0;

        // Open list values
        List<Point> openList = new ArrayList<Point>();
        List<Double> openList_f = new ArrayList<Double>();
        List<Double> openList_g = new ArrayList<Double>();
        List<Point> openList_p = new ArrayList<Point>(); // Parents

        // Closed list values
        List<Point> closedList = new ArrayList<Point>();
        List<Double> closedList_f = new ArrayList<Double>();
        List<Point> closedList_p = new ArrayList<Point>(); // Parents

        // Check begin and goal tiles
        Tile beginTile = getTile(origin);
        Tile goalTile = getTile(goal);

        // If either begin or goal tile are not found or non walkable, return empty path
        if (beginTile == null || goalTile == null || !beginTile.isWalkable() || !goalTile.isWalkable()) {
            return closedList;
        }

        // If origin is goal

        if (origin.equals(goal)) {
            closedList.add(origin);
            return closedList;
        }

        // Add begin tile position to open list
        openList.add(origin);
        openList_f.add(0.0);
        openList_g.add(0.0);
        openList_p.add(origin);

        // Iterate until open list empty or goal reached
        boolean end = false;
        while (!end && openList.isEmpty() == false && iter < maxIter) {
            // Find node with lowest f on the open list (node q)
            double minF = 0.0;

            Point q = new Point(0, 0);
            int q_i = 0;
            double q_g = 0.0;
            double g_f = 0.0;
            Point q_p = new Point(0, 0);

            for (int i = 0; i < openList.size(); i++) {
                // Get f value
                double f = openList_f.get(i);

                // Choose tile with min F
                if (i == 0 || f < minF) {
                    minF = f;

                    q_i = i;
                    q = openList.get(q_i);
                    q_g = openList_g.get(i);
                    g_f = openList_f.get(i);
                    q_p = openList_p.get(i);
                }
            }

            // Pop q from the open list
            openList.remove(q_i);
            openList_f.remove(q_i);
            openList_g.remove(q_i);
            openList_p.remove(q_i);

            // Generate q's successors and set their parents to q
            List<Point> successors = new ArrayList<Point>();

            if (allowDiagonals) {//8 successors (diagonal movement allowed)
                for (int x = -1; x <= 1; x++) {
                    for (int y = -1; y <= 1; y++) {
                        if (x != 0 || y != 0) {
                            Point s = new Point(x + q.x, y + q.y);

                            Tile sTile = getTile(s);
                            if (sTile != null && sTile.isWalkable())
                                successors.add(s);
                        }
                    }
                }

            } else { // 4 successors (up/down movement)
                for (int x = -1; x <= 1; x++) {
                    if (x != 0) {
                        Point s = new Point(x + q.x, q.y);

                        Tile sTile = getTile(s);
                        if (sTile != null && sTile.isWalkable())
                            successors.add(s);
                    }
                }
                for (int y = -1; y <= 1; y++) {
                    if (y != 0) {
                        Point s = new Point(q.x, y + q.y);

                        Tile sTile = getTile(s);
                        if (sTile != null && sTile.isWalkable())
                            successors.add(s);
                    }
                }
            }
            // For each successor
            for (int i = 0; i < successors.size(); i++) {
                Point s = successors.get(i);

                /*i) if successor is the goal, stop search */
                if (s.equals(goal)) {
                    ////System.out.println("FOUND GOAL!");
                    end = true;
                    break;
                }

                /*successor.g = q.g + distance between
                successor and q
                successor.h = distance from goal to
                successor (This can be done using many
                        ways, we will discuss three heuristics-
                                Manhattan, Diagonal and Euclidean
                        Heuristics)

                successor.f = successor.g + successor.h*/
                double s_g = q_g + q.distance(s);
                double s_h = s.distance(goal);
                double s_f = s_g + s_h;


                /*ii) if a node with the same position as
                successor is in the OPEN list which has a
                lower f than successor, skip this successor*/
                boolean contains = false;
                for (int j = 0; j < openList.size(); j++) {
                    Point o = openList.get(j);
                    if (o.equals(s) && openList_f.get(j) <= s_f) {
                        contains = true;
                        break;
                    }
                }
                if (contains) {
                    continue;
                }

                /*iii) if a node with the same position as
                successor  is in the CLOSED list which has
                a lower f than successor, skip this successor
                otherwise, add  the node to the open list*/
                contains = false;
                for (int j = 0; j < closedList.size(); j++) {
                    Point o = closedList.get(j);
                    if (o.equals(s) && closedList_f.get(j) <= s_f) {
                        contains = true;
                        break;
                    }
                }
                if (contains) {
                    continue;
                }

                openList.add(s);
                openList_f.add(s_f);
                openList_g.add(s_g);
                openList_p.add(q);
            }

            // Push q to the closed list
            closedList.add(q);
            closedList_f.add(g_f);
            closedList_p.add(q_p);

            // If end reached, add goal to closed list
            if (end) {
                closedList.add(goal);
                closedList_f.add(g_f);
                closedList_p.add(q);
            }
            iter++;
        }

        // End not reached
        if (!end) {
            return new ArrayList<Point>();
        }

        // Iterate through parents to get path to return
        List<Point> parentList = new ArrayList<Point>();
        int c_i = closedList.size() - 1;
        Point child = closedList.get(c_i);
        Point parent = closedList_p.get(c_i);
        while (parent != child) {
            parentList.add(child);

            child = parent;
            c_i = closedList.indexOf(child);
            parent = closedList_p.get(c_i);
        }
        parentList.add(child);

        return parentList;
    }

    public List<Vector3> a_star(Vector3 origin, Vector3 goal) {
        List<Vector3> globalRes = new ArrayList<>();
        List<Point> tilePosList = a_star_local(globalToTilePos(origin), globalToTilePos(goal), false);
        for (int i = 0; i < tilePosList.size(); i++) {
            globalRes.add(tileToGlobalPos(tilePosList.get(i)));
        }
        return globalRes;
    }

    Pair<Integer, String> readString(ByteBuffer bf, int readingPos) {
        StringBuilder res = new StringBuilder();
        byte[] c = new byte[1];
        do {
            c[0] = bf.get(readingPos++);
            res.append(new String(c, StandardCharsets.UTF_8));
        }
        while (c[0] != 0xA);
        return new Pair<>(readingPos, res.toString());
    }

    /* CS2D map format: http://www.unrealsoftware.de/files_pub/cs2d_spec_map_format.txt */
    public void load(String mapName) throws IOException {
        int newLineIndex = 0;
        int readingPos = 0;
        Pair<Integer, String> p;
        List<Integer> newLinePositions = new ArrayList<>();
        String path = "Maps/" + mapName + ".map";
        System.out.println("Loading map: " + mapName);
        File file = new File(Map.class.getClassLoader().getResource(path).getFile());
        long fileSize = file.length();

        InputStream is = Map.class.getClassLoader().getResourceAsStream(path);
        byte[] fileBytes = new byte[is.available()];
        is.read(fileBytes);

        ByteBuffer bf = ByteBuffer.wrap(fileBytes);
        bf = bf.order(ByteOrder.LITTLE_ENDIAN);
        String aux = new String(bf.array());
        for (int pos = aux.indexOf("\n"); pos != -1; pos = aux.indexOf("\n", pos + 1)) {
            newLinePositions.add(pos + 1);
        }
        //System.out.println(newLinePositions);
        /*(1) HEADER*/
        p = readString(bf, readingPos);
        readingPos = p.getKey();
        String headerString = p.getValue();
        //System.out.println(headerString);
        /*10 bytes for map settings/info*/
        Byte scrollMapLikeTiles = bf.get(readingPos++);
        //System.out.println(scrollMapLikeTiles);
        Byte useModifiersInThisMap = bf.get(readingPos++);
        //System.out.println(useModifiersInThisMap);
        Byte saveTileHeightsWithMap = bf.get(readingPos++);
        //System.out.println(saveTileHeightsWithMap);
        Byte use64x64PixelTiles = bf.get(readingPos++);
        //System.out.println(use64x64PixelTiles);
        readingPos += 6; //6 Unused BYTES

        /*10 ints for map settings/info*/
        int uptimeOfSystem = bf.getInt(readingPos);
        readingPos += 4;
        //System.out.println(uptimeOfSystem);
        int authorUSGNId = bf.getInt(readingPos);
        readingPos += 4;
        //System.out.println(authorUSGNId);
        int mapDayLight = bf.getInt(readingPos);
        readingPos += 4;
        //System.out.println(mapDayLight);
        readingPos = readingPos + (4 * 7); //7 Unused INTs

        /*10 strings for map settings/info*/
        p = readString(bf, readingPos);
        readingPos = p.getKey();
        String mapAuthor = p.getValue();
        //System.out.print(mapAuthor);
        //String programUsed = aux.substring(readingPos, newLinePositions.get(newLineIndex));
        p = readString(bf, readingPos);
        readingPos = p.getKey();
        String programUsed = p.getValue();
        //System.out.print(programUsed);
        for (int i = 0; i < 8; i++) { //8 Unused STRINGs
            p = readString(bf, readingPos);
            readingPos = p.getKey();
        }

        /*More map settings*/
        p = readString(bf, readingPos);
        readingPos = p.getKey();
        String sizeTime = p.getValue();
        //System.out.print(sizeTime);
        p = readString(bf, readingPos);
        readingPos = p.getKey();
        String tileSetImage = p.getValue();
        //System.out.print(tileSetImage);
        Byte tilesRequired = bf.get(readingPos++);
        //System.out.println(tilesRequired);
        int mapWidth = bf.getInt(readingPos);
        readingPos += 4;
        //System.out.println(mapWidth);
        int mapHeight = bf.getInt(readingPos);
        readingPos += 4;
        //System.out.println(mapHeight);
        p = readString(bf, readingPos);
        readingPos = p.getKey();
        String backgroundName = p.getValue();
        //System.out.print(backgroundName);
        int backgroundScrollXSpeed = bf.getInt(readingPos);
        readingPos += 4;
        //System.out.println(backgroundScrollXSpeed);
        int backgroundScrollYSpeed = bf.getInt(readingPos);
        readingPos += 4;
        //System.out.println(backgroundScrollYSpeed);
        Byte backgroundRed = bf.get(readingPos++);
        Byte backgroundGreen = bf.get(readingPos++);
        Byte backgroundBlue = bf.get(readingPos++);
        double r = backgroundRed & 0xFF;
        double g = backgroundGreen & 0xFF;
        double b = backgroundBlue & 0xFF;
        if (r > 1) r = r / 255.0;
        if (g > 1) g = g / 255.0;
        if (b > 1) b = b / 255.0;
        //System.out.println("RGB:" + backgroundRed + "," + backgroundGreen + "," + backgroundBlue);
        this.backgroundColor = new javafx.scene.paint.Color(r, g, b, 1.0);
        /*Header Test*/
        p = readString(bf, readingPos);
        readingPos = p.getKey();
        String headerTest = p.getValue();
        //System.out.print(headerTest);
        //System.out.println("HEADER DONE");

        int tileCount = Integer.parseInt(sizeTime.substring(sizeTime.indexOf("x") + 1, sizeTime.indexOf("$")));

        /*(2) TILE MODES*/
        Byte[] tileModes = new Byte[tileCount + 1];
        for (int i = 0; i < tileCount + 1; i++) {
            tileModes[i] = bf.get(readingPos++);
        }
        //System.out.println("TILE MODES DONE");


        /*(3) TILE HEIGHTS*/
        if (saveTileHeightsWithMap == 1) {
            int[] tileHeights = new int[tileCount + 1];
            for (int i = 0; i < tileCount + 1; i++) {
                tileHeights[i] = bf.getInt(readingPos);
                readingPos += 4;
            }
        } else if (saveTileHeightsWithMap == 2) {
            short[] tileHeights = new short[tileCount + 1];
            Byte[] tileModifier = new Byte[tileCount + 1];
            for (int i = 0; i < tileCount + 1; i++) {
                tileHeights[i] = bf.getShort(readingPos);
                readingPos += 2;
                tileModifier[i] = bf.get(readingPos++);
            }
        } else {
            short[] tileHeights = new short[tileCount + 1];
            for (int i = 0; i < tileCount + 1; i++) {
                short height = 0;
                //WALL
                if ((tileModes[i] & 0xFF) == 1 || (tileModes[i] & 0xFF) == 3 || (tileModes[i] & 0xFF) == 5) {
                    height = 32;
                } else if ((tileModes[i] & 0xFF) == 2 || (tileModes[i] & 0xFF) == 4) {
                    height = 16;
                }
                tileHeights[i] = height;
            }
        }
        //System.out.println("HEIGHTS DONE");

        /*(4) MAP*/
        Byte[][] tileIndex = new Byte[mapWidth + 1][mapHeight + 1];
        data = new Tile[mapWidth + 1][mapHeight + 1];
        for (int x = 0; x <= mapWidth; x++) {
            for (int y = 0; y <= mapHeight; y++) {
                tileIndex[x][y] = bf.get(readingPos++);
                data[x][y] = new Tile(tileModes[tileIndex[x][y] & 0xFF], tileIndex[x][y] & 0xFF);
            }
        }

        if ((useModifiersInThisMap & 0xFF) == 1) {
            //System.out.println("in");
            Byte[][] tileModifier = new Byte[mapWidth + 2][mapHeight + 2];
            for (int x = 0; x < mapWidth + 2; x++) {
                for (int y = 0; y < mapHeight + 2; y++) {
                    tileModifier[x][y] = bf.get(readingPos++);
                    if ((tileModifier[x][y] >> 8 & 1) == 1 && (tileModifier[x][y] >> 7 & 1) == 1) {
                        readingPos = newLinePositions.get(newLineIndex++); //Unused STRING
                    } else if ((tileModifier[x][y] >> 8 & 1) == 0 && (tileModifier[x][y] >> 7 & 1) == 1) {
                        //TODO: Asignar
                        bf.get(readingPos++);
                    } else {
                        //TODO: Asignar
                        bf.get(readingPos++);
                        bf.get(readingPos++);
                        bf.get(readingPos++);
                        bf.get(readingPos++);
                    }
                }
            }
        }
        //System.out.println("MAP DONE");

        /*(5) ENTITIES*/
        int entityCount = bf.getInt(readingPos);
        readingPos += 4;
        //System.out.println("ENTITYCOUNT: " + entityCount);
        String[] entityNames = new String[entityCount];
        Byte[] entityTypes = new Byte[entityCount];
        int[] entityXPositions = new int[entityCount];
        int[] entityYPositions = new int[entityCount];
        String[] entityTriggers = new String[entityCount];
        for (int i = 0; i < entityCount; i++) {
            p = readString(bf, readingPos);
            readingPos = p.getKey();
            entityNames[i] = p.getValue();
            entityTypes[i] = bf.get(readingPos++);
            entityXPositions[i] = bf.getInt(readingPos);
            readingPos += 4;
            entityYPositions[i] = bf.getInt(readingPos);
            readingPos += 4;
            //System.out.print("ENTITY NAME " + i + ": " + entityNames[i]);
            //System.out.println("ENTITY TYPE: " + (entityTypes[i] & 0xFF));
            //System.out.println("ENTITY POS: (" + entityXPositions[i] + "," + entityYPositions[i] + ")");
            p = readString(bf, readingPos);
            readingPos = p.getKey();
            entityTriggers[i] = p.getValue();
            int[] entityInts = new int[10];
            for (int j = 0; j < 10; j++) {
                //TODO: Asignar
                entityInts[j] = bf.getInt(readingPos);
                readingPos += 4;
                p = readString(bf, readingPos);
                readingPos = p.getKey();
                String xAsignar = p.getValue();
            }
            if (entityTypes[i] == 0) {
                //TODO: Z se usa para guardar rotacion, cuidado
                tSpawn.add(new Vector3(entityXPositions[i] + tileSize / 2, entityYPositions[i] + tileSize / 2, entityInts[0]));
            } else if (entityTypes[i] == 1) {
                ctSpawn.add(new Vector3(entityXPositions[i] + tileSize / 2, entityYPositions[i] + tileSize / 2, entityInts[0]));
            } else if (entityTypes[i] == 5) {
                addMapBombPoint(new Vector3(entityXPositions[i], entityYPositions[i], entityInts[0]));
            }
        }

        tileWH = 32;
        if (use64x64PixelTiles == 1) {
            tileWH = 64;
        }
        nTilesW = mapWidth + 1;
        nTilesH = mapHeight + 1;

        String tileSetImagePath = "Maps/tiles/" + tileSetImage.trim();
        //System.out.println("Loading tile image file from: " + tileSetImagePath);
        Image source = new Image(Map.class.getClassLoader().getResourceAsStream(tileSetImagePath));
        //source = Transparency.makeColorTransparent(source, new Color(255,0,255));
        source = GraphicsUtils.changeColor(source, Color.rgb(255, 0, 255), backgroundColor);
        int width = (int) source.getWidth();
        int height = (int) source.getHeight();
        tileImages = new Image[(height / tileWH) * (width / tileWH)];
        int idx = 0;
        for (int y = 0; y < height; y += tileWH) {
            for (int x = 0; x < width; x += tileWH, idx++) {
                tileImages[idx] = GraphicsUtils.getSubimage(source, x, y, tileWH, tileWH);
            }
        }

        String tileBackgroundImage = "Maps/backgrounds/" + backgroundName.trim();
        //System.out.println("BACKGROUND IMAGE: " + tileBackgroundImage);

        source = new Image(Map.class.getClassLoader().getResourceAsStream(tileBackgroundImage));
        this.background = source;

    }


    public Vector3 getRandomWalkablePosition(Random rand) {
        //Random rand = new Random(System.nanoTime()); //instance of random class
        int xRandom = rand.nextInt(nTilesW);
        int yRandom = rand.nextInt(nTilesH);
        while (!data[xRandom][yRandom].isWalkable() || xRandom < 2 || xRandom > nTilesW - 2 || yRandom < 2 || yRandom > nTilesH - 2) {//TODO: Las comprobaciones de x e y son temporales
            xRandom = rand.nextInt(nTilesW);
            yRandom = rand.nextInt(nTilesH);
        }
        return new Vector3(xRandom * tileSize + 0.5, yRandom * tileSize + 0.5, 0); //TODO: Usar z real
    }

    public List<Vector3> gettSpawn() {
        return tSpawn;
    }

    public List<Vector3> getctSpawn() {
        return ctSpawn;
    }

    public List<BombPoint> getBombPoints() {
        return bombPoints;
    }

    public Image getBackgroundImage() {
        return this.background;
    }

    public javafx.scene.paint.Color getBackgroundColor() {
        return this.backgroundColor;
    }

    public String getName() {
        return this.name;
    }
}
